#include <TflQuery.h>
#include "FunTrace.h"
#include <aknglobalnote.h>
#include <e32math.h>
#include <eikimage.h>
#include <akndialog.h>
#include <aknquerycontrol.h>
#include <aknnumedwin.h>
#include <es_sock.h>
#include <escapeutils.h>
#include <ezgzip.h>
#include <SingaporeBus.rsg>
#include <aknquerydialog.h>
#include <eikapp.h>

const TInt DndTimedOut = -5120;
const TInt kTflTimeout = 45*1000000;
const TInt kCrowdTflTimeout = 10*1000000;

#define EAknEditorFlagEnablePartialScreen             0x200000

#include <ImageConversion.h>
#include <euserhl.h>
#include "Binder.h"


static void PrintNoteL(TRefByValue<const TDesC> aFormat, ...)
    {
    VA_LIST list;
    VA_START(list, aFormat);
    TBuf<256> buf;
    buf.FormatList(aFormat, list);
    VA_END(list);
	CAknGlobalNote* globalNote = CAknGlobalNote::NewLC();
	globalNote->ShowNoteL(EAknGlobalInformationNote, buf); 
    CleanupStack::PopAndDestroy(globalNote);
    }


static TBool IsSpace(TChar ch)
	{
	switch(ch)
		{
		case ' ': case '\n': case '\t': case '\r':
			return ETrue;
			break;
		default:
			return EFalse;
		}
	}

static TPtrC8 IrisTrim( const TDesC8& aBuf)
	{
	TInt beg, end;
	for(beg = 0 ; beg < aBuf.Length() && IsSpace( aBuf[beg] ) ; ++beg);
	if(beg == aBuf.Length()) return aBuf.Mid(0, 0);
	for(end = aBuf.Length() - 1 ; end > beg && IsSpace( aBuf[end] ) ; --end);
	return aBuf.Mid(beg, end + 1 - beg);
	}


static TPtrC8 IrisExtractField(const TDesC8& aPrefix, const TDesC8& aSuffix, TPtrC8& aBuf)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	TPtrC8 ret;
	TInt begfield = aBuf.Find(aPrefix);
	if(begfield != KErrNotFound)
		{
		TInt endfield = aBuf.Mid(begfield + aPrefix.Length()).Find(aSuffix);
		if(endfield != KErrNotFound)
			{
			ret.Set( aBuf.Mid(begfield + aPrefix.Length(), endfield) );
			aBuf.Set( aBuf.Mid( begfield + aPrefix.Length() + endfield + aSuffix.Length() ) );
			return ret;
			}
		}
	aBuf.Set( aBuf.Mid( aBuf.Length() ) );
	return ret;
	}


_LIT(kFormatRequestPending, "\trequesting...");
_LIT(kFormatRequestScheduled, "\tqueued, select to request");
_LIT(kFormatRequestScheduledWaitingConnection, "\tqueued, waiting connection");
_LIT(kFormatRequestWait, "\tselect to request");
_LIT(kFormatRequestSchedule, "\tevery %S");
_LIT(kFormatRequestCompleted, "\t%S \xf814 %S");
_LIT(kFormatRequestBlocked, "\tunexpected answer (probably blocked by SBS or Tfl), please check whether the app has been updated");
_LIT(kFormatRequestPendingCaptcha, "\trequesting captcha...");
_LIT(kFormatRequestDownloadingCaptcha, "\tdownloading captcha...");
_LIT(kFormatCaptchaError, "\tcaptcha processing error");
_LIT(kFormatRequestSubmittingCaptcha, "\tsubmitting captcha...");
_LIT(kFormatSolvingCaptcha, "\tsolving captcha...");
_LIT(kFormatRequestError, "\terror (%S, %S)");
_LIT(kFormatRequestUnavailable, "\tschedule unavailable");
_LIT(kFormatRequestPendingInitialization, "\twaiting for initialization...");
_LIT(kFormatRequestPendingToken, "\trequesting token...");
_LIT(kFormatRequestPendingCrowdToken, "\trequesting crowd token...");
_LIT(kFormatRequestSelectingBus, "\tselecting bus...");



/**
 * First phase of Symbian two-phase construction. Should not contain any
 * code that could leave.
 */
CTflSession::CTflSession()
	{
	iTflConnectTimeoutTimer.CreateLocal();

	iTflTimeoutTimerHelper = NULL;
	
	}

/** 
 * The view's destructor removes the container from the control
 * stack and destroys it.
 */
CTflSession::~CTflSession()
	{
	
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	
	
	delete iTflTimeoutTimerHelper;
	iTflConnectTimeoutTimer.Close();
	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		delete iTflQueries[i];
	iTflQueries.Close();
	iHTTPSession.Close();
	}

CTflSession* CTflSession::NewL()
	{
TFunTrace funTrace(__PRETTY_FUNCTION__);
	CTflSession* self = CTflSession::NewLC();
	CleanupStack::Pop( self );
	return self;
	}

CTflSession* CTflSession::NewLC()
	{
TFunTrace funTrace(__PRETTY_FUNCTION__);
	CTflSession* self = new ( ELeave ) CTflSession();
	CleanupStack::PushL( self );
	self->ConstructL();
	return self;
	}



void CTflSession::NotifyConnected()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		if(iTflQueries[i]->iRequestStatus == CTflQuery::ERequestError)
			SubmitL(iTflQueries[i]);
	}

void CTflSession::CloseConnectionL()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	CancelAllTflRequestsL();
	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		if( (iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPending || iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPendingInitialization))
			{
			_LIT8 (connectionClosed, "connection closed");
			iTflQueries[i]->iRequestStatus = CTflQuery::ERequestError;
			(*iTflQueries[i]->iCallback.Get())(iTflQueries[i], (*iTflQueries[i]->iStationNumber), (*iTflQueries[i]->iServiceName), CTflQuery::ERequestError, (*&kFormatRequestError()), (*&connectionClosed), (*&connectionClosed));
			}
	iHTTPSession.Close();
	}

void CTflSession::SetConnectionInfoL(const RConnection& aConnection, const RSocketServ& aSocketServ)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iHTTPSession.OpenL();
	RStringPool strP = iHTTPSession.StringPool();
	RHTTPConnectionInfo connInfo = iHTTPSession.ConnectionInfo();
	connInfo.SetPropertyL ( strP.StringF(HTTP::EHttpSocketServ, RHTTPSession::GetTable() ), THTTPHdrVal (aSocketServ.Handle()) );
	TInt connPtr = REINTERPRET_CAST(TInt, &aConnection);
	connInfo.SetPropertyL ( strP.StringF(HTTP::EHttpSocketConnection, RHTTPSession::GetTable() ), THTTPHdrVal (connPtr) );
	RHTTPSessionSetHeaderL(iHTTPSession, HTTP::EUserAgent,
#include "Tfl_query_user_agent.txt"
			);
	}


void CTflSession::ConstructL()
	{
TFunTrace funTrace(__PRETTY_FUNCTION__);

	iTflTimeoutTimerHelper = CActiveHelper<RTimer*>::NewL();
	iDeflateDecompressor = CDeflateDecompressor::NewL();
	}


void CTflSession::SubmitL(CTflQuery* aQuery)
{
TFunTrace funTrace(__PRETTY_FUNCTION__);

	TInt i;
	for(i = 0 ;
		i < iTflQueries.Count() &&
		(
		iTflQueries[i]->iServiceTransaction == RHTTPTransaction() ||
		iTflQueries[i]->iRequestStatus != CTflQuery::ERequestPending ||
		*iTflQueries[i]->iStationNumber != *aQuery->iStationNumber)
		; ++i);
	if(i < iTflQueries.Count())
		{
		// we already have a pending query taking care of this bus stop
		aQuery->iRequestStatus = CTflQuery::ERequestPending;
		(*aQuery->iCallback.Get())(aQuery, (*aQuery->iStationNumber), (*aQuery->iServiceName), CTflQuery::ERequestPending, (*&kFormatRequestPendingInitialization()), (*&KNullDesC8()), (*&KNullDesC8()));
		}
	else
		SubmitTflConnectRequestL(aQuery);
}


void CTflSession::Cancel(CTflQuery* aQuery)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	if(aQuery->iServiceTransaction != RHTTPTransaction())
		{
		const TDesC8& path = aQuery->iServiceTransaction.Request().URI().Extract(EUriPath);
		TflConnectTransactionError(aQuery, KErrCancel, RHTTPTransaction());
		}

	if(aQuery->iServiceTransaction != RHTTPTransaction())
		{
		aQuery->iServiceTransaction.Cancel();
		aQuery->iServiceTransaction.Close();
		aQuery->iServiceTransaction = RHTTPTransaction();
		}
	}


void CTflSession::CancelAllTflRequestsL()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		if(iTflQueries[i]->iServiceTransaction != RHTTPTransaction())
			{
			iTflQueries[i]->iServiceTransaction.Cancel();
			iTflQueries[i]->iServiceTransaction.Close();
			iTflQueries[i]->iServiceTransaction = RHTTPTransaction();
			}
	iTflTimeoutTimerHelper->Cancel();
	}


TBool CTflSession::AllRequestsCompleted() const
{
TFunTrace funTrace(__PRETTY_FUNCTION__);

	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		if(iTflQueries[i]->iServiceTransaction != RHTTPTransaction())
			return EFalse;
	return ETrue;
}


void CTflSession::MActiveHelperCallbackRunL(CActiveHelper<RTimer*>* aActiveHelper, RTimer* aTimer)
{
TFunTrace funTrace(__PRETTY_FUNCTION__);

if(aActiveHelper->iStatus != KErrNone) return;

	if(aActiveHelper == iTflTimeoutTimerHelper)
		{
		CancelAllTflRequestsL();
		for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
			if( (iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPending || iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPendingInitialization))
				SubmitL(iTflQueries[i]);
		}

}

TInt CTflSession::MActiveHelperCallbackRunError(CActiveHelper<RTimer*>* aActiveHelper, RTimer* aTimer, TInt aError)
{
TFunTrace funTrace(__PRETTY_FUNCTION__);
}

void CTflSession::MActiveHelperCallbackDoCancel(CActiveHelper<RTimer*>* aActiveHelper, RTimer* aTimer)
{
TFunTrace funTrace(__PRETTY_FUNCTION__);
	aTimer->Cancel();
}



void CTflSession::SubmitTflConnectRequestL(CTflQuery* aQuery)
{
TFunTrace funTrace(__PRETTY_FUNCTION__);
	TBuf8<1024> uristr;
	uristr.Format(
	#include "Tfl_query_url.txt"
			, aQuery->iStationNumber);
	if(aQuery->iServiceTransaction != RHTTPTransaction())
		{
		aQuery->iServiceTransaction.Cancel();
		aQuery->iServiceTransaction.Close();
		}
	delete aQuery->iServiceTransactionTarget;
	aQuery->iServiceTransactionTarget = CHTTPTransactionTarget::NewL();
	aQuery->iServiceTransactionTarget->SetRun( scalp::MakeFunctionL( scalp::Bind(&CTflSession::ReceivedTflConnectAnswerL, this, aQuery, scalp::Free1, scalp::Free2, scalp::Free3, scalp::Free4) ) );
	aQuery->iServiceTransactionTarget->SetRunBodyData(NULL);
	aQuery->iServiceTransactionTarget->SetRunError( scalp::MakeFunctionL( scalp::Bind(&CTflSession::TflConnectTransactionError, this, aQuery, scalp::Free1, scalp::Free2) ) );			
	aQuery->iServiceTransaction = RHTTPSessionOpenTransactionL(iHTTPSession, uristr, *aQuery->iServiceTransactionTarget, HTTP::EGET);
	RHTTPRequestSetHeaderL(iHTTPSession, aQuery->iServiceTransaction.Request(), HTTP::EAcceptLanguage, _L8("en-us"));
	RHTTPRequestSetHeaderL(iHTTPSession, aQuery->iServiceTransaction.Request(), HTTP::EAccept, _L8("*/*"));
	RHTTPRequestSetHeaderL(iHTTPSession, aQuery->iServiceTransaction.Request(), HTTP::EAcceptEncoding, _L8("gzip, deflate"));
	aQuery->iServiceTransaction.SubmitL();				
	aQuery->iRequestStatus = CTflQuery::ERequestPending;
	(*aQuery->iCallback.Get())(aQuery, (*aQuery->iStationNumber), (*aQuery->iServiceName), CTflQuery::ERequestPending, (kFormatRequestPending()), (KNullDesC8()), (KNullDesC8()));
	iTflTimeoutTimerHelper->Cancel();
	iTflConnectTimeoutTimer.After(iTflTimeoutTimerHelper->iStatus, kTflTimeout);
	iTflTimeoutTimerHelper->SetActive(this, &iTflConnectTimeoutTimer);
}

struct TTflService
	{
	TPtrC8 iServiceName;
	TPtrC8 iArrival1, iArrival2;
	};

void CTflSession::ReceivedTflConnectAnswerL(CTflQuery* aQuery, RHTTPTransaction aTransaction, THTTPEvent aEvent, TInt aLastError, TPtrC8 aBuf)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);

	iTflTimeoutTimerHelper->Cancel();
	iTflConnectTimeoutTimer.After(iTflTimeoutTimerHelper->iStatus, kTflTimeout);
	iTflTimeoutTimerHelper->SetActive(this, &iTflConnectTimeoutTimer);
	aQuery->iServiceTransaction = RHTTPTransaction();
	iTflStatusCode = aTransaction.Response().StatusCode();
	if(aEvent.iStatus != THTTPEvent::ESucceeded || iTflStatusCode != 200)
		User::Leave(aLastError < 0 ? aLastError : KErrAbort);

	const TDesC8& requestPath = aTransaction.Request().URI().Extract(EUriPath);
	if(requestPath.Find(_L8("stopBoard")) == KErrNotFound)
		User::Leave(KErrAccessDenied);
	
	TPtrC8 smrtConnectBuf;
	
	THTTPHdrVal contentEncodingHdrVal;
	TInt ret = aTransaction.Response().GetHeaderCollection().GetField(
					aTransaction.Session().StringPool().StringF(HTTP::EContentEncoding, RHTTPSession::GetTable()), 0,
					contentEncodingHdrVal
					);
	const TDesC8& contentEncoding( ret == KErrNone ? contentEncodingHdrVal.StrF().DesC() : KNullDesC8);
	if(ret == KErrNone && contentEncoding == _L8("gzip"))
		smrtConnectBuf.Set( iDeflateDecompressor->DecompressGZipL(aBuf) );
	else
		smrtConnectBuf.Set( aBuf );

	LCleanedupHandle< RArray<TTflService> > services;
	
	{
	TPtrC8 buf = smrtConnectBuf, serviceName;
	
	for(
			serviceName.Set( IrisExtractField(_L8("\"routeName\":\""), _L8("\""), buf) ) ;
			serviceName.Length() ;
			serviceName.Set( IrisExtractField(_L8("\"routeName\":\""), _L8("\""), buf) )
			)
		{
				
		TPtrC8 tail = buf;
		TPtrC8 arrival = IrisExtractField(_L8("\"estimatedWait\":\""), _L8("\""), tail);

		TInt i;
		for (i = 0 ; i < services->Count() && (*services)[i].iServiceName != serviceName ; ++i);
		if(i != services->Count())
			{
			if( !(*services)[i].iArrival2.Length() ) (*services)[i].iArrival2.Set(arrival);
			}
		else
			{
			TTflService service = { serviceName, arrival, KNullDesC8() };
			services->AppendL(service);
			}
		}
		
	for(TInt j = 0 ; j < services->Count(); ++j)
		{
		for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
			{
			TBuf8<128> queryStationNumber = *iTflQueries[i]->iStationNumber;
			if(		iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPending &&
					queryStationNumber == *aQuery->iStationNumber &&
					iTflQueries[i]->iServiceName->CompareF( (*services)[j].iServiceName ) ==0
					)
				{
				iTflQueries[i]->iRequestStatus = CTflQuery::ERequestCompleted;
				(*iTflQueries[i]->iCallback.Get())(iTflQueries[i], (*iTflQueries[i]->iStationNumber), (*iTflQueries[i]->iServiceName), CTflQuery::ERequestCompleted, (*&kFormatRequestCompleted()), (*services)[j].iArrival1, (*services)[j].iArrival2);
				}
			}
		}
	
	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		{
		if(		iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPending &&
				*iTflQueries[i]->iStationNumber == *aQuery->iStationNumber
				)
			{
			iTflQueries[i]->iRequestStatus = CTflQuery::ERequestUnavailable;
			(*iTflQueries[i]->iCallback.Get())(iTflQueries[i], (*iTflQueries[i]->iStationNumber), (*iTflQueries[i]->iServiceName), CTflQuery::ERequestUnavailable, (*&kFormatRequestUnavailable()), KNullDesC8(), KNullDesC8());
			}
		}
	
	}
		
	aTransaction.Cancel();
	aTransaction.Close(); // note: if a panic is raised before reaching this line, MHFRunError will close the transaction
	}


TInt CTflSession::TflConnectTransactionError(CTflQuery* aQuery, TInt aError, RHTTPTransaction aTransaction)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	TBuf8<64> lastErrorStr, statusCodeStr;
	lastErrorStr.Format(_L8("%d"), aError);
	statusCodeStr.Format(_L8("%d"), iTflStatusCode);

	for(TInt i = 0 ; i < iTflQueries.Count() ; ++i)
		{
		if(		iTflQueries[i]->iRequestStatus == CTflQuery::ERequestPending &&
				*iTflQueries[i]->iStationNumber == *aQuery->iStationNumber
				)
			{
			iTflQueries[i]->iRequestStatus = CTflQuery::ERequestError;
			(*iTflQueries[i]->iCallback.Get())(iTflQueries[i], (*iTflQueries[i]->iStationNumber), (*iTflQueries[i]->iServiceName), CTflQuery::ERequestError, (*&kFormatRequestError()), (*&lastErrorStr), (*&statusCodeStr));
			}
		}
	
	return KErrNone;
	
	}


CTflQuery::CTflQuery(CTflSession* aSession, const TDesC8& aServiceName, const TDesC8& aStationNumber, TCallback* aCallback)
: iSession(aSession), iServiceName(NULL), iStationNumber(NULL), iCallback(aCallback)
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iServiceName = aServiceName.AllocLC();
	iStationNumber = aStationNumber.AllocLC();
	CleanupStack::Pop(2);
	iSession->iTflQueries.AppendL(this);
	}

void CTflQuery::SubmitL()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iSession->SubmitL(this);
	}
void CTflQuery::Cancel()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	iSession->Cancel(this);
	}

CTflQuery::~CTflQuery()
	{
	TFunTrace funTrace(__PRETTY_FUNCTION__);
	TRAPD(err, Cancel()); // ignore leaves
	delete iServiceName;
	delete iStationNumber;
	delete iServiceTransactionTarget;
	TInt i;
	for(i = 0 ; i < iSession->iTflQueries.Count() && iSession->iTflQueries[i] != this ; ++i);
	if(i < iSession->iTflQueries.Count())
		iSession->iTflQueries.Remove(i);
	}


